package compiler;

import java.util.Arrays;

/**
 * Custom token reader.
 * 
 * This is a festering pile of pants, really. Any sensible language would do
 * decent String processing.
 * 
 * @author Sarah Mount <s.mount@wlv.ac.uk>
 * @version 16 Dec 2009
 */
public class TokenReader {

	/** Input stream. */
	private final char[] program;

	/** Current position in input stream. */
	private int charno = 0;

	public TokenReader(String program) {
		this.program = program.toCharArray();
	}

	/**
	 * Consume a semi-colon (or new line character) from the input stream.
	 * 
	 * <p>
	 * TODO: Ensure that this works with both UNIX and Windows newlines.
	 * </p>
	 */
	public void consumeSemiColon() {
		this.consumeWhitespace();
		while (this.charno < this.program.length
				&& (this.program[this.charno] == ';' || this.program[this.charno] == '\n')) {
			this.charno++;
		}
	}

	/**
	 * Consume and return a single String from the input stream.
	 * 
	 * @return a trimmed string to be cast to a HowlToken.#
	 * @see compiler.HowlToken
	 */
	public String consumeToken() {
		this.consumeWhitespace();
		int start = this.charno;
		while (this.charno < this.program.length
				&& this.program[this.charno] != ';'
				&& !Character.isWhitespace(this.program[this.charno])) {
			this.charno++;
		}
		return String.valueOf(Arrays.copyOfRange(this.program, start,
				this.charno));
	}

	/**
	 * Consume (and ignore) any whitespace at the current position in the
	 * stream.
	 */
	private void consumeWhitespace() {
		while (this.charno < this.program.length
				&& this.program[this.charno] != ';'
				&& Character.isWhitespace(this.program[this.charno])) {
			this.charno++;
		}
	}

	/**
	 * Get the current position of the lexer / parser in the input stream. Used
	 * when generating error messages, to place a carat below the point at which
	 * a syntax error was found.
	 * 
	 * @return current index in input stream.
	 * @see compiler.exceptions.HowlParserException
	 */
	public int getCharNo() {
		return this.charno;
	}

	/**
	 * Get the current statement that is being read. Used when generating a
	 * syntax error message so the user can see where an error was found by the
	 * parser.
	 * 
	 * <p>
	 * TODO: this is slightly broken and does not show the whole statement from
	 * start to semicolon or newline. Needs to be tested on sequences of
	 * statements separated by semicolons and long statements.
	 * </p>
	 * 
	 * @return string representation of current statement that has been read "so
	 *         far".
	 * @see compiler.exceptions.HowlParserException
	 */
	public String getStatement() {
		return String.valueOf(Arrays.copyOfRange(this.program, 0,
				this.charno + 1));
	}

	/**
	 * Determine whether or not the token reader is at the end of a statement.
	 * Used to parse statements which end in a Kleene star.
	 * 
	 * @return true if the token reader is currently at the end of a statement.
	 */
	public boolean hasEndStmt() {
		this.consumeWhitespace();
		return !this.hasMoreTokens() || this.program[this.charno] == ';'
				|| this.program[this.charno] == '\n';
	}

	/**
	 * Determine whether or not the token reader has reached the end of the
	 * input stream.
	 * 
	 * @return true if there are no more tokens to parse.
	 */
	public boolean hasMoreTokens() {
		this.consumeWhitespace();
		return this.charno < this.program.length - 1;
	}

	/**
	 * Return the next token in the stream WITHOUT changing this.charno.
	 * 
	 * @return next token in the stream.
	 */
	public String nextToken() {
		this.consumeWhitespace();
		int start = this.charno;
		int end = start;
		while (end < this.program.length && this.program[end] != ';'
				&& !Character.isWhitespace(this.program[end])) {
			end++;
		}
		return String.valueOf(Arrays.copyOfRange(this.program, start, end));
	}
}
